{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "导入库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import random\n",
    "from sklearn import preprocessing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "导入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index(['checking_status', 'duration_new', 'credit_history', 'purpose',\n",
      "       'credit_amount', 'credit_amount_new', 'savings_status', 'employment',\n",
      "       'installment_commitment', 'personal_status', 'other_parties',\n",
      "       'residence_since', 'property_magnitude', 'age', 'other_payment_plans',\n",
      "       'housing', 'existing_credits', 'job', 'own_telephone', 'foreign_worker',\n",
      "       'class'],\n",
      "      dtype='object')\n",
      "(1000, 21)\n",
      "['A11' 6.0 'A34' 'A43' 1169 1 'A65' 'A75' 4 'A93' 'A101' 4 'A121' 67\n",
      " 'A143' 'A152' 2 'A173' 'A192' 'A201' 0]\n"
     ]
    }
   ],
   "source": [
    "dataset=pd.read_csv('german_clean.csv',header=0)\n",
    "header=dataset.columns\n",
    "dataset=np.array(dataset)\n",
    "dataset[:,-1]=dataset[:,-1]-1# 将标签变为0和1\n",
    "print(header)\n",
    "print(dataset.shape)\n",
    "print(dataset[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数据预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.0, 0.03780718336483932, 1.0, 0.008108108108108109,\n",
       "       0.0505667436997909, 0.0, 1.0, 1.0, 1.0, 0.6666666666666679, 0.0,\n",
       "       1.0, 0.0, 0.8571428571428572, 1.0, 0.5, 0.3333333333333333,\n",
       "       0.6666666666666643, 1.0, 0.0, 0], dtype=object)"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 将离散值变成连续值\n",
    "for i in [0,2,3,6,7,9,10,12,14,15,17,18,19]:\n",
    "    dataset[:,i]=[int(k[1:]) for k in dataset[:,i]]\n",
    "# 数据归一化\n",
    "min_max_scaler = preprocessing.MinMaxScaler()\n",
    "dataset[:,:-1] = min_max_scaler.fit_transform(dataset[:,:-1])\n",
    "dataset[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.针对数据集german_clean，随机采样100次，利用Relief方法，给出特征重要性程度的排序。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "计算样本在特征A上的差值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def diff(a,r1,r2):\n",
    "    return abs(r1[a]-r2[a])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "寻找最近邻样本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_neighbors(dataset,row_data):\n",
    "    minindex=0\n",
    "    mindiff=10000.0\n",
    "    for i in range(dataset.shape[0]):\n",
    "        min=0\n",
    "        for j in range(dataset.shape[1]):\n",
    "            min+=diff(j,dataset[i],row_data)\n",
    "        if min<mindiff and min>0.0:\n",
    "            minindex=i\n",
    "            mindiff=min\n",
    "    return minindex\n",
    "        \n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Relief算法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def relief(dataset,m,gama):\n",
    "    T=[]\n",
    "    W=np.zeros(shape=dataset.shape[1]-1)\n",
    "    for i in range(m):\n",
    "        index=random.randint(0,dataset.shape[0]-1)\n",
    "        R=dataset[index]#当前样本\n",
    "        data_true=dataset[dataset[:,-1]==dataset[index,-1]]# 获取正确样本\n",
    "        data_false=dataset[dataset[:,-1]!=dataset[index,-1]]# 获取错误样本\n",
    "        H=data_true[get_neighbors(data_true[:,:-1],R)]# 同类最近邻样本\n",
    "        M=data_false[get_neighbors(data_false[:,:-1],R)]# 异类最近邻样本\n",
    "        for a in range(dataset.shape[1]-1):\n",
    "            W[a]=W[a]-diff(a,R,H)/m+diff(a,R,M)/m\n",
    "    for a in range(dataset.shape[1]-1):\n",
    "        if W[a]>=gama:\n",
    "            T.append(a)\n",
    "    return T,W\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "获得特征重要性排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.10333333,  0.02778828,  0.03      ,  0.00113514,  0.00038186,\n",
       "       -0.01      ,  0.1125    ,  0.0325    ,  0.05      , -0.02      ,\n",
       "       -0.005     ,  0.04      , -0.00333333, -0.00589286,  0.02      ,\n",
       "        0.025     ,  0.01666667,  0.00333333,  0.06      ,  0.03      ])"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "T,W=relief(dataset,100,0.3)\n",
    "W"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12, 10, 13, 5, 9]\n",
      "['savings_status', 'checking_status', 'own_telephone', 'installment_commitment', 'residence_since', 'employment', 'foreign_worker', 'duration_new', 'housing', 'other_payment_plans', 'existing_credits', 'job', 'purpose', 'credit_amount', 'property_magnitude', 'other_parties', 'age', 'credit_amount_new', 'personal_status']\n"
     ]
    }
   ],
   "source": [
    "num = dict(zip(W,[i for i in range(20)]))# 将特征权重作为key，下标作为value建立字典\n",
    "TZ=[(num[k]) for k in sorted(num.keys(),reverse=True)]# 将字典按照key排序，得到特征重要性的排序(从大到小)\n",
    "TZ_header=[(header[k]) for k in TZ]\n",
    "print(TZ)\n",
    "print(TZ_header)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.结合第1步得到的特征重要性排序，将数据集的前700个数据作为训练集，后300个数据作为验证集，给出用Logistic回归方法进行分类的最佳特征子集。（注意数据集目标字段的标签是用1,2来表示，要修改成0,1）\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "划分训练集和验证集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(700, 20) (700,) (300, 20) (300,)\n"
     ]
    }
   ],
   "source": [
    "train_date=dataset[:700,:-1]\n",
    "train_label=dataset[:700,-1]\n",
    "val_data=dataset[700:,:-1]\n",
    "val_label=dataset[700:,-1]\n",
    "print(train_date.shape,train_label.shape,val_data.shape,val_label.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Logistic回归"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "def sigmoid(z):#映射函数\n",
    "    return 1 / (1 + np.exp(-z.astype('float')))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "def model(X, theta):#模型\n",
    "    return sigmoid(np.dot(X, theta.T))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "def loss_t(X, y, theta):#损失函数\n",
    "    left = np.multiply(-y, np.log(model(X, theta)))\n",
    "    right = np.multiply(1 - y, np.log(1 - model(X, theta)))\n",
    "    return np.sum(left - right) / (len(X))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gradient(X, y, theta):#梯度方向\n",
    "    grad = np.zeros(theta.shape) \n",
    "    error = (model(X, theta)- y).ravel()\n",
    "    for j in range(len(theta.ravel())): #for each parmeter\n",
    "        term = np.multiply(error, X[:,j])\n",
    "        grad[j] = np.sum(term) / len(X)\n",
    "    \n",
    "    return grad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def acc(y,y_label):\n",
    "    for i in range(len(y)):\n",
    "        if y[i]>0.5:\n",
    "            y[i]=1\n",
    "        else:\n",
    "            y[i]=0\n",
    "    return sum([y[i]==y_label[i] for i in range(len(y))])/len(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(x,y,theta,lr,epoch,TZ_theta):\n",
    "    #梯度下降求解\n",
    "    grad = np.zeros(theta.shape) # 计算的梯度\n",
    "    preloss=100000.0\n",
    "    for e in range(epoch):\n",
    "        grad = gradient(x, y, theta)\n",
    "        theta = theta - lr*grad # 参数更新\n",
    "        theta=np.multiply(theta,TZ_theta)\n",
    "        loss_=loss_t(x,y,theta)\n",
    "        if loss_>preloss:\n",
    "            break\n",
    "        preloss=loss_\n",
    "    return theta,loss_t(x,y,theta),acc(model(x,theta),y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(x,y,theta):\n",
    "    loss_ = loss_t(x,y,theta)\n",
    "    acc_ = acc(model(x,theta),y)\n",
    "    return loss_,acc_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "按重要性逐个选择特征进行训练和测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当前特征子集为:[6]，训练集loss:0.632，训练集准确率:0.7，测试集loss:0.5948，测试集准确率:0.69\n",
      "当前特征子集为:[6, 0]，训练集loss:0.5452，训练集准确率:0.7，测试集loss:0.5327，测试集准确率:0.69\n",
      "当前特征子集为:[6, 0, 18]，训练集loss:0.5449，训练集准确率:0.7，测试集loss:0.5331，测试集准确率:0.72\n",
      "当前特征子集为:[6, 0, 18, 8]，训练集loss:0.5418，训练集准确率:0.71，测试集loss:0.5281，测试集准确率:0.73\n",
      "当前特征子集为:[6, 0, 18, 8, 11]，训练集loss:0.5406，训练集准确率:0.72，测试集loss:0.5311，测试集准确率:0.72\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7]，训练集loss:0.5351，训练集准确率:0.72，测试集loss:0.5327，测试集准确率:0.71\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19]，训练集loss:0.5315，训练集准确率:0.72，测试集loss:0.5304，测试集准确率:0.71\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1]，训练集loss:0.5157，训练集准确率:0.75，测试集loss:0.5109，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15]，训练集loss:0.515，训练集准确率:0.75，测试集loss:0.5097，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14]，训练集loss:0.5096，训练集准确率:0.75，测试集loss:0.5074，测试集准确率:0.74\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16]，训练集loss:0.5096，训练集准确率:0.75，测试集loss:0.5073，测试集准确率:0.74\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17]，训练集loss:0.5085，训练集准确率:0.75，测试集loss:0.5065，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3]，训练集loss:0.5084，训练集准确率:0.75，测试集loss:0.5065，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4]，训练集loss:0.5059，训练集准确率:0.75，测试集loss:0.5017，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12]，训练集loss:0.5033，训练集准确率:0.75，测试集loss:0.4931，测试集准确率:0.77\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12, 10]，训练集loss:0.502，训练集准确率:0.75，测试集loss:0.4909，测试集准确率:0.77\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12, 10, 13]，训练集loss:0.5012，训练集准确率:0.75，测试集loss:0.4902，测试集准确率:0.77\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12, 10, 13, 5]，训练集loss:0.4976，训练集准确率:0.75，测试集loss:0.484，测试集准确率:0.77\n",
      "当前特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12, 10, 13, 5, 9]，训练集loss:0.495，训练集准确率:0.75，测试集loss:0.4845，测试集准确率:0.76\n",
      "最优特征子集为:[6, 0, 18, 8, 11, 7, 19, 1, 15, 14, 16, 17, 3, 4, 12, 10, 13, 5]，训练集loss:0.4976，训练集准确率:0.75，测试集loss:0.484，测试集准确率:0.77\n"
     ]
    }
   ],
   "source": [
    "best_TZ=[]\n",
    "best_TZ_acc=0\n",
    "best_TZ_info=[0,0,0,0]\n",
    "for i in range(len(TZ)):\n",
    "    theta=np.zeros(train_date.shape[1])\n",
    "    TZ_theta=np.zeros(train_date.shape[1])\n",
    "    for j in range(i+1):\n",
    "        TZ_theta[TZ[j]]=1\n",
    "    theta,loss_train,acc_train=train(train_date,train_label,theta,1e-1,1000,TZ_theta)\n",
    "    loss_val,acc_val=test(val_data,val_label,theta)\n",
    "    if acc_val>=best_TZ_acc:\n",
    "        best_TZ=TZ[:i+1]\n",
    "        best_TZ_acc=acc_val\n",
    "        best_TZ_info=[loss_train,acc_train,loss_val,acc_val]\n",
    "    print(f\"当前特征子集为:{TZ[:i+1]}，训练集loss:{loss_train:.4}，训练集准确率:{acc_train:.2}，测试集loss:{loss_val:.4}，测试集准确率:{acc_val:.2}\")\n",
    "print(f\"最优特征子集为:{best_TZ}，训练集loss:{best_TZ_info[0]:.4}，训练集准确率:{best_TZ_info[1]:.2}，测试集loss:{best_TZ_info[2]:.4}，测试集准确率:{best_TZ_info[3]:.2}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "优化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当前特征子集为:[6]，训练集loss:0.6319，训练集准确率:0.7，测试集loss:0.594，测试集准确率:0.69\n",
      "当前特征子集为:[6, 0]，训练集loss:0.5452，训练集准确率:0.7，测试集loss:0.5331，测试集准确率:0.69\n",
      "当前特征子集为:[6, 0, 18]，训练集loss:0.5448，训练集准确率:0.7，测试集loss:0.5335，测试集准确率:0.72\n",
      "当前特征子集为:[6, 0, 18, 8]，训练集loss:0.5416，训练集准确率:0.71，测试集loss:0.528，测试集准确率:0.73\n",
      "当前特征子集为:[6, 0, 18, 8, 11]，训练集loss:0.5404，训练集准确率:0.72，测试集loss:0.5316，测试集准确率:0.72\n",
      "当前特征子集为:[6, 0, 18, 8, 7]，训练集loss:0.5347，训练集准确率:0.72，测试集loss:0.5344，测试集准确率:0.7\n",
      "当前特征子集为:[6, 0, 18, 8, 19]，训练集loss:0.5348，训练集准确率:0.71，测试集loss:0.5266，测试集准确率:0.74\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1]，训练集loss:0.5233，训练集准确率:0.73，测试集loss:0.5051，测试集准确率:0.74\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15]，训练集loss:0.5193，训练集准确率:0.75，测试集loss:0.502，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 14]，训练集loss:0.5104，训练集准确率:0.75，测试集loss:0.4988，测试集准确率:0.74\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16]，训练集loss:0.5192，训练集准确率:0.75，测试集loss:0.5011，测试集准确率:0.76\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 17]，训练集loss:0.5188，训练集准确率:0.74，测试集loss:0.5013，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3]，训练集loss:0.5192，训练集准确率:0.75，测试集loss:0.5011，测试集准确率:0.76\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 4]，训练集loss:0.5175，训练集准确率:0.74，测试集loss:0.4966，测试集准确率:0.75\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 12]，训练集loss:0.5173，训练集准确率:0.75，测试集loss:0.4928，测试集准确率:0.77\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 12, 10]，训练集loss:0.5146，训练集准确率:0.76，测试集loss:0.489，测试集准确率:0.77\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 12, 10, 13]，训练集loss:0.5117，训练集准确率:0.75，测试集loss:0.4897，测试集准确率:0.76\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 12, 10, 5]，训练集loss:0.5084，训练集准确率:0.76，测试集loss:0.4809，测试集准确率:0.78\n",
      "当前特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 12, 10, 5, 9]，训练集loss:0.5009，训练集准确率:0.74，测试集loss:0.4843，测试集准确率:0.78\n",
      "最优特征子集为:[6, 0, 18, 8, 19, 1, 15, 16, 3, 12, 10, 5, 9]，训练集loss:0.5009，训练集准确率:0.74，测试集loss:0.4843，测试集准确率:0.78\n"
     ]
    }
   ],
   "source": [
    "best_TZ=[]\n",
    "best_TZ_acc=0\n",
    "best_TZ_info=[1000,0,1000,0]\n",
    "i=0\n",
    "while i < len(TZ):\n",
    "    theta=np.zeros(train_date.shape[1])\n",
    "    TZ_theta=np.zeros(train_date.shape[1])\n",
    "    for j in range(i+1):\n",
    "        TZ_theta[TZ[j]]=1\n",
    "    theta,loss_train,acc_train=train(train_date,train_label,theta,1e-1,5000,TZ_theta)\n",
    "    loss_val,acc_val=test(val_data,val_label,theta)\n",
    "    print(f\"当前特征子集为:{TZ[:i+1]}，训练集loss:{loss_train:.4}，训练集准确率:{acc_train:.2}，测试集loss:{loss_val:.4}，测试集准确率:{acc_val:.2}\")\n",
    "    if acc_val>best_TZ_acc:\n",
    "        best_TZ=TZ[:i+1]\n",
    "        best_TZ_acc=acc_val\n",
    "        best_TZ_info=[loss_train,acc_train,loss_val,acc_val]\n",
    "        i+=1\n",
    "    elif acc_val==best_TZ_acc and loss_val<=best_TZ_info[2]:\n",
    "        best_TZ=TZ[:i+1]\n",
    "        best_TZ_acc=acc_val\n",
    "        best_TZ_info=[loss_train,acc_train,loss_val,acc_val]\n",
    "        i+=1\n",
    "    else:\n",
    "        del TZ[i]\n",
    "print(f\"最优特征子集为:{best_TZ}，训练集loss:{best_TZ_info[0]:.4}，训练集准确率:{best_TZ_info[1]:.2}，测试集loss:{best_TZ_info[2]:.4}，测试集准确率:{best_TZ_info[3]:.2}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "0ea2ea13f89004d253f237dbfddddbdca6ade9e7c69d0cba8f71d034288dfba7"
  },
  "kernelspec": {
   "display_name": "Python 3.8.8 64-bit ('tensorflow': conda)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
